package com.ccx.demo.business.user.entity;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import com.ccx.demo.business.common.vo.UserFileInfo;
import com.ccx.demo.business.common.vo.convert.UserFileInfoJsonConvert;
import com.ccx.demo.business.user.vo.UserDetail;
import com.ccx.demo.enums.RegisterSource;
import com.ccx.demo.enums.TokenClient;
import com.ccx.demo.open.auth.cache.TokenCache;
import com.google.common.collect.Lists;
import com.querydsl.core.annotations.QueryEntity;
import com.querydsl.core.annotations.QueryTransient;
import com.querydsl.core.types.*;
import com.querydsl.core.types.dsl.BeanPath;
import com.querydsl.core.types.dsl.ComparableExpressionBase;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAUpdateClause;
import com.support.mvc.encrypt.AesApiId;
import com.support.mvc.entity.ITable;
import com.support.mvc.entity.IUser;
import com.support.mvc.entity.IWhere;
import com.support.mvc.entity.IWhere.QdslWhere;
import com.support.mvc.entity.base.OrderBy;
import com.support.mvc.entity.convert.AesDbConvert;
import com.support.mvc.entity.convert.ArrayStringJsonConvert;
import com.utils.enums.Code;
import com.utils.util.Dates;
import com.utils.util.Then;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.ToString;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.domain.Sort;
import org.springframework.util.CollectionUtils;

import javax.persistence.*;
import javax.validation.constraints.Positive;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import static com.ccx.demo.business.user.entity.QTabUser.tabUser;
import static com.utils.util.Dates.Pattern.yyyyMMddHHmmss;
import static com.utils.util.Dates.Pattern.yyyyMMddHHmmssSSS;
import static io.swagger.annotations.ApiModelProperty.AccessMode.READ_ONLY;

/**
 * 用户实体
 *
 * @author 谢长春
 */
//@Table(name = "tab_user", uniqueConstraints = {@UniqueConstraint(columnNames = "uuid")})
@Table(name = "tab_user")
@ApiModel(description = "用户表")
@Entity
@QueryEntity
@DynamicInsert
@DynamicUpdate
@Getter
@Setter
@ToString(exclude = {"password"})
@JSONType(orders = {"id", "username", "nickname", "phone", "email", "avatar", "roles", "registerSource", "createTime", "createUserId", "updateTime", "updateUserId", "hidden", "deleted"})
public class TabUser extends UserDetail implements ITable, IUser, IWhere<JPAUpdateClause, QdslWhere> {

    private static final long serialVersionUID = 1945320644170494162L;
    /**
     * 数据ID
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Positive
    @ApiModelProperty(value = "数据ID", position = 1)
    private Long id;
//    @Id
//    @GeneratedValue(generator = "base18")
//    @GenericGenerator(name = "base18", strategy = "com.support.mvc.entity.generator.Base18Generator")
//    @Size(min = 18, max = 18)
//    //@JSONField(serializeUsing = AesLongJsonSerializer.class, deserializeUsing = AesLongJsonDeserializer.class)
//    @ApiModelProperty(value = "数据ID", position = 1)
//    private String id;
    /**
     * 登录名
     */
    @Size(max = 15)
    @ApiModelProperty(value = "登录名", position = 3)
    private String username;
    /**
     * 登录密码
     */
    @Column(updatable = false)
    @Size(max = 1024)
    @ApiModelProperty(value = "登录密码", position = 4)
    private String password;
    /**
     * 用户昵称
     */
    @Size(max = 30)
    @ApiModelProperty(value = "昵称", position = 5)
    private String nickname;
    /**
     * 手机号
     */
    @Column(updatable = false)
    @Size(max = 11)
    @Convert(converter = AesDbConvert.class)
    @ApiModelProperty(value = "手机号", position = 6)
    private String phone;
    /**
     * 邮箱
     */
    @Column(updatable = false)
    @Size(max = 30)
    @ApiModelProperty(value = "邮箱", position = 7)
    private String email;
    /**
     * 用户头像
     */
    @Convert(converter = UserFileInfoJsonConvert.class)
    @ApiModelProperty(value = "用户头像", position = 8)
    private UserFileInfo avatar;
    /**
     * 角色 ID 集合，tab_role.id，{@link String}[]
     * 角色 ID 集合，tab_role.id {@link TabRole#getId()}
     */
    @Convert(converter = ArrayStringJsonConvert.class)
    @ApiModelProperty(value = "角色 ID 集合，tab_role.id，{@link String}[]", position = 9)
    private String[] roles;
    /**
     * 注册渠道
     */
    @Column(updatable = false)
    @ApiModelProperty(value = "账户注册渠道", hidden = true, position = 10)
    private RegisterSource registerSource;
    /**
     * 创建时间
     */

    @JSONField(serialize = false, deserialize = false)
    @ApiModelProperty(value = "数据新增时间，格式:yyyyMMddHHmmss", example = "20200202020202", position = 11)
    private String createTime;
    /**
     * 创建用户ID
     */
    @Column(updatable = false)
    @PositiveOrZero
    @ApiModelProperty(value = "新增操作人id", position = 12)
    private Long createUserId;
    /**
     * 修改时间
     */
    @ApiModelProperty(value = "数据最后一次更新时间，格式:yyyyMMddHHmmssSSS", example = "20200202020202002", position = 13)
    private String updateTime;
    /**
     * 修改用户ID
     */
    @PositiveOrZero
    @ApiModelProperty(value = "更新操作人id", position = 14)
    private Long updateUserId;
    /**
     * 是否为隐藏用户，禁止编辑且前端列表不展示。 [0:否,1:是]
     */
    @ApiModelProperty(hidden = true)
    private Boolean hidden;
    /**
     * disabled       TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '',
     */
    @ApiModelProperty(value = "是否禁用账号，禁用账号无法登录。 [0:否,1:是]", position = 16)
    private Boolean disabled;
    /**
     * 是否逻辑删除。 [0:否,1:是]
     */
    @ApiModelProperty(value = "是否逻辑删除。 [0:否,1:是]", position = 16)
    private Boolean deleted;

    /**
     * 扩展查询字段：按 id 批量查询，仅后端使用，对前端隐藏
     */
    @QueryTransient
    @Transient
    @ApiModelProperty(hidden = true)
    @JSONField(serialize = false, deserialize = false)
    private Set<Long> ids;
    /**
     * 排序字段
     */
    @QueryTransient
    @Transient
    @ApiModelProperty(value = "查询排序字段", position = 18)
    private List<OrderBy> orderBy;
    /**
     * 新增操作人昵称
     */
    @Transient
    @QueryTransient
    @JSONField(deserialize = false)
    @ApiModelProperty(value = "新增操作人昵称", position = 14, accessMode = READ_ONLY)
    private String createUserNickname;
    /**
     * 更新操作人昵称
     */
    @Transient
    @QueryTransient
    @JSONField(deserialize = false)
    @ApiModelProperty(value = "更新操作人昵称", position = 14, accessMode = READ_ONLY)
    private String updateUserNickname;

    /**
     * 防止踩坑。
     * 当 JPA 查询对象没有 clone 时，执行 setValue 操作时，会触发更新动作，这里拦截之后抛出异常
     */
    @PreUpdate
    public void onPreUpdate() {
        throw Code.A00003.toCodeException("禁止使用JPA默认的 update 方法。 如果业务逻辑确定需要使用 setValue 方式更新对象时，去掉这个异常");
    }

    /**
     * 加密id
     *
     * @return String
     */
    @Transient
    @QueryTransient
    @JSONField(deserialize = false)
    @ApiModelProperty(value = "加密id", position = 1, accessMode = READ_ONLY)
    public String getEncryptId() {
        return AesApiId.encrypt(id, getUpdateTime());
    }

    @Override
    public TabUser loadUserDetail() {
        return this;
    }

    @SneakyThrows
    public TabUser cloneObject() {
        return (TabUser) super.clone();
    }

    // Enum Start **********************************************************************************************************

    /**
     * 枚举：定义排序字段
     */
    public enum OrderByColumn {
        // 按 id 排序可替代按创建时间排序
        id(tabUser.id),
        //        username(tabUser.username),
//        password(tabUser.password),
//        nickname(tabUser.nickname),
//        phone(tabUser.phone),
//        email(tabUser.email),
//        avatar(tabUser.avatar),
//        roles(tabUser.roles),
//        registerSource(tabUser.registerSource),
//        createTime(tabUser.createTime),
//        createUserId(tabUser.createUserId),
//        updateTime(tabUser.updateTime),
//        updateUserId(tabUser.updateUserId),
//        hidden(tabUser.hidden),
        deleted(tabUser.deleted),
        ;
        private final ComparableExpressionBase<?> column;

        public OrderBy asc() {
            return new OrderBy().setName(this.name());
        }

        public OrderBy desc() {
            return new OrderBy().setName(this.name()).setDirection(Sort.Direction.DESC);
        }

        /**
         * 获取所有排序字段名
         *
         * @return {@link String[]}
         */
        public static String[] names() {
            return Stream.of(OrderByColumn.values()).map(Enum::name).toArray(String[]::new);
        }

        OrderByColumn(final ComparableExpressionBase<?> column) {
            this.column = column;
        }
    }

// Enum End : DB Start *************************************************************************************************

    /**
     * DAO层新增数据时，设置字段默认值
     *
     * @param now    当前时间
     * @param userId 当前操作用户id
     */
    public void insert(final Dates now, final Long userId) {
        this.id = null;
        this.nickname = Optional.ofNullable(this.nickname).orElse("");
        this.phone = Optional.ofNullable(this.phone).orElse("");
        this.email = Optional.ofNullable(this.email).orElse("");
        this.registerSource = Optional.ofNullable(this.registerSource).orElse(RegisterSource.SYSTEM);
        this.hidden = Optional.ofNullable(this.hidden).orElse(false);
        this.disabled = Optional.ofNullable(this.disabled).orElse(false);
        this.deleted = Optional.ofNullable(this.deleted).orElse(false);
        this.createUserId = userId;
        this.createTime = now.format(yyyyMMddHHmmss);
        this.updateUserId = userId;
        this.updateTime = now.format(yyyyMMddHHmmssSSS);
    }

    @Override
    public Then<JPAUpdateClause> update(final JPAUpdateClause jpaUpdateClause) {
        final QTabUser table = tabUser;
        return Then.of(jpaUpdateClause)
                .then(nickname, update -> update.set(table.nickname, nickname))
                .then(phone, update -> update.set(table.phone, phone))
                .then(email, update -> update.set(table.email, email))
                .then(avatar, update -> update.set(table.avatar, avatar))
                .then(roles, update -> update.set(table.roles, roles))
//                .then(update -> update.set(table.updateUserId, updateUserId))
                ;
    }

    @Override
    public QdslWhere where() {
        final QTabUser table = tabUser;
        // 构建查询顺序规则请参考：com.support.mvc.entity.IWhere#where
        return QdslWhere.of()
                .andIfNonEmpty(ids, () -> table.id.in(ids))
                .and(id, () -> table.id.eq(id))
                .and(username, () -> table.username.eq(username))
                .and(phone, () -> table.phone.eq(phone))
                .and(email, () -> table.email.eq(email))
                .and(registerSource, () -> table.registerSource.eq(registerSource))
                .and(createUserId, () -> table.createUserId.eq(createUserId))
                .and(updateUserId, () -> table.updateUserId.eq(updateUserId))
                .and(hidden, () -> table.hidden.eq(hidden))
                .and(disabled, () -> table.disabled.eq(disabled))
                .and(table.deleted.eq(Optional.ofNullable(deleted).orElse(false)))
                .and(nickname, () -> table.nickname.containsIgnoreCase(nickname))
                .and(roles, () -> {
                    QdslWhere.Or exps = QdslWhere.Or.of();
                    // Expressions.booleanTemplate("JSON_EXTRACT({0}, '$[0]') IS NULL > 0", table.roles);
                    for (String roleId : roles) {
                        exps.or(Expressions.booleanTemplate("JSON_CONTAINS({0},JSON_ARRAY({1}))>0", table.roles, Objects.toString(roleId)));
                    }
                    return exps.toPredicate();
                })
//              Expressions.booleanTemplate("JSON_CONTAINS({0},{1})>0", table.roles, roleId)
//              Expressions.booleanTemplate("JSON_CONTAINS({0},{1})>0", table.roles, JSON.toJSONString(roles))
////                // 强制带默认值的查询字段
////                .and(table.deleted.eq(Optional.ofNullable(deleted).orElse(false)))
////                // 数字区间查询
////                .and(amountRange, () -> table.amount.between(amountRange.getMin(), amountRange.getMax()))
////                // 日期区间查询；Range.rebuild() : 先将时间区间重置到 00:00:00.000 - 23:59:59.999 ; 大多数情况都需要重置时间
////                .and(createTimeRange, () -> ExpressionUtils.and(createTimeRange.rebuild().ifPresentCreateTimeBegin(table.createTime::goe), createTimeRange.ifPresentCreateTimeEnd(table.createTime::loe)))
////                .and(updateTimeRange, () -> ExpressionUtils.and(updateTimeRange.rebuild().ifPresentUpdateTimeBegin(table.createTime::goe), updateTimeRange.ifPresentUpdateTimeEnd(table.createTime::loe)))
////                // 模糊匹配查询：后面带 % ；建议优先使用
////                .and(name, () -> table.name.startsWith(name)) // 模糊匹配查询：后面带 %
////                .and(name, () -> table.name.endsWith(name)) // 模糊匹配查询：前面带 %
////                .and(name, () -> table.name.contains(name)) // 模糊匹配查询：前后带 %,同 MessageFormat.format("%{0}%", name)
////                .and(name, () -> table.name.like(MessageFormat.format("%{0}%", name))) 模糊匹配查询：前后带 %

                ;
    }

    /**
     * 排序参数构建：QueryDSL 查询模式；QueryDSL 模式构建排序对象返回 null 则会抛出异常
     *
     * @return {@link OrderSpecifier[]}
     */
    public OrderSpecifier<?>[] qdslOrderBy() {
        try {
            if (CollectionUtils.isEmpty(orderBy)) {
                // return new OrderSpecifier<?>[]{OrderByColumn.id.column.asc()}; // 指定默认排序字段
                return new OrderSpecifier<?>[]{};
            }
            return orderBy.stream().map((OrderBy by) -> {
                final OrderByColumn column = OrderByColumn.valueOf(by.getName());
                // 自定义排序
                // new OrderSpecifier<>(Order.valueOf(by.getDirection().name()) , Expressions.stringTemplate("convert_gbk({0})", column.column));

                // return Objects.equals(orderBy.getDirection(), Sort.Direction.DESC) ? column.column.desc(): column.column.asc();
                return new OrderSpecifier<>(Order.valueOf(by.getDirection().name()), column.column);
            }).toArray(OrderSpecifier<?>[]::new);
        } catch (Exception e) {
            throw Code.A00003.toCodeException("排序字段可选范围：".concat(JSON.toJSONString(OrderByColumn.names())));
        }
    }

    /**
     * 排序参数构建：Spring JPA 查询模式；JPA 模式构建排序对象返回 null 表示不排序
     *
     * @return {@link Sort}
     */
    public Sort jpaOrderBy() {
        try {
            if (CollectionUtils.isEmpty(orderBy)) {
                // return Sort.by(Sort.Direction.ASC, OrderByColumn.id.name()); // 指定默认排序字段
                return Sort.unsorted();
            }
            return orderBy.stream()
                    .map((OrderBy by) -> {
                        final OrderByColumn column = OrderByColumn.valueOf(by.getName());
                        // 自定义排序
                        // JpaSort.unsafe(by.getDirection(), String.format("convert_gbk(%s)", column.name()));
                        return Sort.by(by.getDirection(), column.name());
                    })
                    .reduce(Sort::and)
                    .orElseGet(Sort::unsorted);
        } catch (Exception e) {
            throw Code.A00003.toCodeException("排序字段可选范围：".concat(JSON.toJSONString(OrderByColumn.names())));
        }
    }

    /**
     * 基于用户生成 token，同一端每个用户只支持一个 token ，每次调用该方法，前一次生成的 token 将失效
     *
     * @return {@link String} 生成的 token
     */
    public String token(final TokenClient client) {
        return TokenCache.builder()
                .client(client)
                .userId(this.id)
                .build()
                .token();
    }

    /**
     * 获取查询实体与数据库表映射的所有字段,用于投影到 VO 类
     * 支持追加扩展字段,追加扩展字段一般用于连表查询
     *
     * @param appends {@link Expression}[] 追加扩展连表查询字段
     * @return {@link Expression}[]
     */
    public static Expression<?>[] allColumnAppends(final Expression<?>... appends) {
        final List<Expression<?>> columns = Lists.newArrayList(appends);
        final Class<?> clazz = tabUser.getClass();
        try {
            for (Field field : clazz.getDeclaredFields()) {
                if (field.getType().isPrimitive()) continue;
                final Object o = field.get(tabUser);
                if (o instanceof EntityPath || o instanceof BeanPath) continue;
                if (o instanceof Path) {
                    columns.add((Path<?>) o);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("获取查询实体属性与数据库映射的字段异常", e);
        }
        return columns.toArray(new Expression<?>[0]);
    }

// DB End **************************************************************************************************************

}
/* yml 缓存配置
spring:
  app:
    cache:
      tab-user: # ITabUserCache.CACHE_ROW_BY_ID 缓存配置
        expired: 1d
        heap: 1000
        offheap: 1
        disk: 100
*/
/* ehcache 配置
.withCache(ITabUserCache.CACHE_ROW_BY_ID, CacheConfigurationBuilder
        .newCacheConfigurationBuilder(Long.class, TabUser.class, ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(appConfig.getCache().getTabUser().getHeap(), EntryUnit.ENTRIES) // 堆内内存，没有反序列化性能消耗，但是不能对读取的缓存对象执行 set 操作
                .offheap(appConfig.getCache().getTabUser().getOffheap(), MemoryUnit.MB) // 堆外内存，有反序列化消耗，同样不能对读取的对象执行 set 操作
                .disk(appConfig.getCache().getTabUser().getDisk(), MemoryUnit.MB, true) // 缓存到磁盘，指定缓存文件最大值
        )
        .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(appConfig.getCache().getTabUser().getExpired())) // 过期时间
        .withValueSerializer(new FastJsonEhcacheSerializer<>(TabUser.class)) // 序列化规则
)
*/
/* redis 配置
.withCacheConfiguration(ITabUserCache.CACHE_ROW_BY_ID, RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(appConfig.getCache().getTabUser().getExpired()) // 设置过期时间
        .disableCachingNullValues() // 设置缓存 null 值
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new CustomFastJsonRedisSerializer<>(TabUser.class)))
)
*/
/*
import com.alibaba.fastjson.annotation.JSONField;
import com.querydsl.core.annotations.QueryTransient;
import com.support.mvc.entity.ICache;
import java.beans.Transient;

import static com.ccx.demo.config.init.AppInit.getBean;
/**
 * 缓存：用户
 *
 * @author 谢长春 on 2021-05-31 V20210530
 *\/
public interface ITabUserCache extends ICache {
    String CACHE_ROW_BY_ID = "ITabUserCache";

    /**
     * 按 ID 获取数据缓存行
     *
     * @param id {@link TabUser#getId()}
     * @return {@link Optional<TabUser>}
     *\/
    @JSONField(serialize = false, deserialize = false)
    default Optional<TabUser> getTabUserCacheById(final Long id) {
        return Optional.ofNullable(id)
                .filter(v -> v > 0)
                .map(v -> getBean(UserRepository.class).findCacheById(v));
    }
}
*/
